﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using System.Text.RegularExpressions;

namespace Science.Mathematics.Algebra
{
    public class Term
    {
        #region Private Data Members

        private double _coeffiecient = 0;
        private Dictionary<String, double> _variables = new Dictionary<string,double>();
        private bool _leadTerm = false;

        #endregion

        #region Properties

        public double Coeffiecient
        {
            get
            {
                return _coeffiecient;
            }
            set
            {
                _coeffiecient = value;
            }
        }

        public bool LeadTerm
        {
            get
            {
                return _leadTerm;
            }
            set
            {
                _leadTerm = value;
            }
        }

        public double this[String variable]
        {
            get
            {
                if (_variables.ContainsKey(variable))
                    return _variables[variable];
                else
                    return 0;
            }
            set
            {
                _variables[variable] = value;
            }
        }

        public double Degree
        {
            get
            {
                double maxPower = Double.MinValue;
                foreach (var entry in _variables)
                    maxPower = maxPower > entry.Value ? maxPower : entry.Value;
                return maxPower;
            }
        }

        #endregion

        #region Constructors

        public Term()
        {
            Coeffiecient = 0;
            LeadTerm = false;
        }

        public Term(double coeffiecient)
        {
            Coeffiecient = coeffiecient;
            LeadTerm = false;
        }

        public Term(double coeffiecient, String varName, double variablePower)
        {
            Coeffiecient = coeffiecient;
            this[varName] = variablePower;
            LeadTerm = false;
        }

        public Term(double coeffiecient, Dictionary<String, double> variables)
        {
            Coeffiecient = coeffiecient;
            _variables = variables;
            LeadTerm = false;
        }

        public Term(String termStr)
        {
            int index = 0;
            Term term = Term.Parse(termStr, ref index);
            Coeffiecient = term.Coeffiecient;
            LeadTerm = false;
            _variables = term._variables;
        }

        #endregion

        #region Public Methods

        public double Eval(String varName, double varValue)
        {
            if (_variables.Count > 1)
                throw new ArgumentException("Not enough values to evaluate term");

            return Coeffiecient * Math.Pow(varValue, this[varName]);
        }

        public double Eval(Dictionary<String, double> varValues)
		{
			if (varValues.Count < _variables.Count)
				throw new ArgumentException("Not enough values to evaluate term");

			double res = Coeffiecient;
			foreach (var entry in varValues)
			{
                if (!varValues.ContainsKey(entry.Key))
					continue;

                res *= Math.Pow(entry.Value, this[entry.Key]);
			}

			return res;
		}

        public bool Like(Term t)
        {
            if (Degree != t.Degree)
                return false;
            foreach (var entry in t._variables)
                if (this[entry.Key] != entry.Value)
                    return false;
            return true;
        }

        public void MakeLike(Term t)
        {
            foreach (var entry in t._variables)
                if (!_variables.ContainsKey(entry.Key))
                    _variables[entry.Key] = 0;
        }

        #endregion

        #region Static Public Function

        public static Term Parse(String str, ref int index)
        {
            if (index >= str.Length)
                throw new InvalidOperationException("Invalid term string");

            Term term = new Term();            
            Match match = Regex.Match(str, @"\s*(-?\d+)?\s*", RegexOptions.IgnoreCase);
            index = match.Index + match.Length;
            if (match.Success)
            {
                if (match.Groups[1].Value == "")
                    term.Coeffiecient = 1;
                else                
                    term.Coeffiecient = double.Parse(match.Groups[1].Value);
                index = match.Index + match.Length;
            }
            else
                term.Coeffiecient = 1;

            str = str.Substring(index);
            match = Regex.Match(str, @"(\w)(\^(-?\d+))?", RegexOptions.IgnoreCase);
            while (match.Success)
            {
                index = match.Index + match.Length;
                String varName = match.Groups[1].Value;
                if (match.Groups[2].Success)
                {
                    String power = match.Groups[3].Value;
                    if (power == "")
                        term[varName] = 1;
                    else
                        term[varName] = Double.Parse(power);
                }
                else
                    term[varName] = 1;
                str = str.Substring(index);
                match = Regex.Match(str, @"(\w)(\^(-?\d+))?", RegexOptions.IgnoreCase);
            }

            return term;
        }

        public static bool TryParse(String str, ref int index, ref Term term)
        {
            try
            {
                term = Term.Parse(str, ref index);
                return true;
            }
            catch
            {
                return false;
            }
        }

        #endregion

        #region Object Virtual Functions

        public override bool Equals(object obj)
        {
            Term term = obj as Term;
            return Coeffiecient == term.Coeffiecient && _variables == term._variables;
        }

        public override int GetHashCode()
        {
            return Coeffiecient.GetHashCode() + _variables.GetHashCode();
        }

        public override string ToString()
        {
            String ret = Coeffiecient == 0 ? "" : Coeffiecient == 1? (_variables.Count == 0? "1" : "") : Coeffiecient.ToString();
            foreach (var entry in _variables)
                ret += (entry.Value == 0 ? "" : entry.Key) + (entry.Value == 1? "" : "^" + entry.Value.ToString());
            return ret;
        }

        #endregion

        #region Overloaded Operator

        #region Logical Operators

        public static bool operator ==(Term x, Term y)
        {
            return x.Coeffiecient == y.Coeffiecient && x._variables == y._variables;
        }

        public static bool operator !=(Term x, Term y)
        {
            return !(x == y);
        }

        public static bool operator <(Term x, Term y)
        {
            return x.Degree < y.Degree;
        }

        public static bool operator >(Term x, Term y)
        {
            return x.Degree > y.Degree;
        }

        public static bool operator <=(Term x, Term y)
        {
            return x.Degree <= y.Degree;
        }

        public static bool operator >=(Term x, Term y)
        {
            return x.Degree >= y.Degree;
        }

        #endregion

        #region Mathematics Operators

        public static Term operator *(Term x, Term y)
        {
            Term t = new Term();
            t.Coeffiecient = x.Coeffiecient * y.Coeffiecient;
            x.MakeLike(y); y.MakeLike(x);
            foreach (var entry in x._variables)
                t[entry.Key] = x[entry.Key] + y[entry.Key];
            return t;
        }

        public static Term operator /(Term x, Term y)
        {
            if ((double)y == 0)
                throw new DivideByZeroException();

            Term t = new Term();
            t.Coeffiecient = x.Coeffiecient / y.Coeffiecient;
            x.MakeLike(y); y.MakeLike(x);
            foreach (var entry in x._variables)
                t[entry.Key] = x[entry.Key] - y[entry.Key];
            return t;
        }

        #endregion

        #region Conversion Operators

        public static implicit operator Term(double x)
        {
            return new Term(x);
        }

        public static explicit operator double(Term x)
        {
            return x.Coeffiecient;
        }

        public static implicit operator Term(String str)
        {
            int index = 0;
            return Term.Parse(str, ref index);
        }

        public static explicit operator String(Term x)
        {
            return x.ToString();
        }

        #endregion

        #endregion
    }
}
